package solverStrategies;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import sudoku.SudokuEinzelfeld;
import sudoku.SudokuSpielfeld;

public class AdvancedReduce implements SolverAlgorithm {

	private final int LEVEL = 1;

	public boolean manipulateSudoku(SudokuSpielfeld sf) {
		boolean changes = false;

		/* Look at every construct */
		for (int i = 0; i < 9; i++) {
			boolean changestmp = false;
			List<SudokuEinzelfeld> row = sf.returnRow(i);

			changestmp = reduceWrapper(row, "Row");
			if (changestmp) {
				changes = true;
			}
			List<SudokuEinzelfeld> col = sf.returnCol(i);
			changestmp = reduceWrapper(col, "Col");
			if (changestmp) {
				changes = true;
			}
			List<SudokuEinzelfeld> block = sf.returnblock(i + 1);
			changestmp = reduceWrapper(block, "Block");
			if (changestmp) {
				changes = true;
			}
		}
		
		

		return changes;
	}

	private boolean reduceWrapper(List<SudokuEinzelfeld> construct, String art) {
		Map<Integer, Set<Integer>> idToein = convertToIdSetMap(construct);
		int hashSet = idToein.hashCode();
		boolean changes = false;
		idToein = reduceSets(idToein);
		int newHashSet = idToein.hashCode();
		if (newHashSet != hashSet) {
			changes = true;
		}
		while (newHashSet != hashSet) {
			newHashSet = hashSet;
			idToein = reduceSets(idToein);
			hashSet = idToein.hashCode();
		}
		return changes;
	}

	private Map<Integer, Set<Integer>> reduceSets(
			Map<Integer, Set<Integer>> idToEin) {
		for (int i = 2; i < 9; i++) {
			checkIfTheyAreEqual(idToEin, i);
		}
		return idToEin;
	}
	
	private Map<Integer, Set<Integer>> buildSubMapWithSize(
			Map<Integer, Set<Integer>> base, int maxSize) {
		Set<Integer> newBase = base.keySet();
		Map<Integer, Set<Integer>> subMap = new HashMap<Integer, Set<Integer>>();
		for (Integer integer : newBase) {
			if (base.get(integer).size() <= maxSize) {
				subMap.put(integer, base.get(integer));
			}
		}
		return subMap;

	}
	
	private Map<Integer, Set<Integer>> removeIdentifiedFromOther(
			Map<Integer, Set<Integer>> idToEin, Set<Integer> toDelete) {
		Set<Integer> keys = idToEin.keySet();
		for (Integer integer : keys) {
			Set<Integer> base = idToEin.get(integer);
			if (base.size() >= toDelete.size() && !base.equals(toDelete)) {
				base.removeAll(toDelete);
			}
		}

		return idToEin;
	}

	private void checkIfTheyAreEqual(Map<Integer, Set<Integer>> idToEin, int i) {

		Map<Integer, Set<Integer>> idToEinBackUp = buildSubMapWithSize(idToEin,
				i);
		Set<Integer> keys = idToEinBackUp.keySet();
		if (!idToEinBackUp.isEmpty()) {
			Map<Integer, Integer> idSetCounter = countDuplicates(idToEinBackUp,
					keys);

			/*
			 * Count how often a set is covered in a construct. If the number of
			 * duplicates are equal the size of this set, all numbers of this
			 * set can deleted from the other sets; except the duplicates
			 */
			keys = idSetCounter.keySet();
			for (Integer integer : keys) {
				Set<Integer> changeSet = idToEin.get(integer);
				if (idSetCounter.get(integer) == changeSet.size()) {
					//deleteFromOtherConstructs(idToEin, integer);
					removeIdentifiedFromOther(idToEin, changeSet);
				}
			}
		}

	}

	private Map<Integer, Integer> countDuplicates(
			Map<Integer, Set<Integer>> idToEin, Set<Integer> keys) {
		Map<Integer, Integer> idSetCounter = new HashMap<Integer, Integer>();
		Set<Integer> actual;
		for (Integer integer : keys) {
			actual = idToEin.get(integer);
			Set<Integer> keys2nd = idToEin.keySet();
			for (Integer integer2nd : keys2nd) {
				Set<Integer> actual2nd = idToEin.get(integer2nd);
				if (actual.equals(actual2nd)) {
					int counterTmp;
					if (idSetCounter.containsKey(integer)) {
						counterTmp = idSetCounter.get(integer);
						idSetCounter.put(integer, ++counterTmp);
					} else {
						idSetCounter.put(integer, 1);
					}
				}
			}
		}
		return idSetCounter;
	}

	private boolean deleteFromOtherConstructs(
			Map<Integer, Set<Integer>> idToEin, Integer integer) {
		boolean changes = false;
		Set<Integer> actual = idToEin.get(integer);
		Set<Integer> keys = idToEin.keySet();
		for (Integer integer2 : keys) {
			Set<Integer> checkSet = idToEin.get(integer2);
			boolean subset = isSubsetSet(actual, checkSet);
			boolean equals = actual.equals(checkSet);
			/*
			 * delete if not equal and no subset If is equal, then subset will
			 * return true => !true || !true = false means that nothing will be
			 * deleted If is not equal but subset (1,2,3 and 1,2) then !false ||
			 * !true = true | false => true (!!!ERROR!!!)
			 */
			if (!equals) {
				if (!subset) {

					boolean tmp = idToEin.get(integer2).removeAll(actual);
					if (tmp) {
						changes = true;
					}
				}
			}
		}
		return changes;

	}
	
	private boolean isSubsetSet(Set<Integer> base, Set<Integer> subset) {
		Set<Integer> baselocal;
		Set<Integer> subsetLocal;
		if (base.size() >= subset.size()) {
			baselocal = base;
			subsetLocal = subset;
		} else {
			baselocal = subset;
			subsetLocal = base;
		}
		boolean containsAll = baselocal.containsAll(subset);
		Set<Integer> subsetBackup = new HashSet<Integer>();
		subsetBackup.addAll(subsetLocal);
		subsetBackup.removeAll(baselocal);
		return subsetBackup.isEmpty() && containsAll;
	}
	
	private Map<Integer, Set<Integer>> convertToIdSetMap(
			List<SudokuEinzelfeld> construct) {

		Map<Integer, Set<Integer>> idToein = new HashMap<Integer, Set<Integer>>();
		for (SudokuEinzelfeld sudokuEinzelfeld : construct) {
			idToein.put(sudokuEinzelfeld.getId(),
					sudokuEinzelfeld.getPossibleNumbers());
		}
		return idToein;

	}

	@Override
	public int returnLevel() {
		return LEVEL;
	}

}
